import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';

import { message } from '@/components/AntdGlobal';
import { getStatusMessage } from '@/http/statusMessage';
import { useUserStore } from '@/store';
import type { IResponseData } from '@/types/api';
import { BizError, getBaseURL } from '@/utils';
import { handleLoading } from '@/utils/loading';

// ============ 公共辅助方法 ============

/**
 * 错误处理函数
 * @param error 错误对象，包含AxiosError和Error类型
 * @param showError 是否显示错误消息
 */
const handleError = (error: AxiosError & Error, showError: boolean) => {
    const { response, request, message: msg } = error;
    if (!showError) return;
    if (response) {
        // 请求成功发出且服务器也响应了状态码，但状态代码超出了 2xx 的范围
        message.error(getStatusMessage(response.status));
    } else if (request) {
        // 请求已经成功发起，但没有收到响应
        message.error('网络请求失败，请稍后再试');
    } else {
        // 其他发送请求问题,或者后端非000000逻辑处理失败
        message.error(msg || '网络请求失败，请稍后再试');
    }
};

/**
 * ============ 1. 创建 axios 实例 ================
 */
const instance = axios.create({
    // 请求的根路径
    baseURL: getBaseURL(),
    // 请求超时时间ms
    timeout: 1000 * 60,
    // 自定义超时提示文案
    timeoutErrorMessage: '请求超时，请稍后再试',
    // 支持跨域请求携带cookie
    withCredentials: true,
    // 请求头配置
    headers: {
        'Content-Type': 'application/json;charset=utf-8'
    }
});

/**
 * ============ 2. 请求拦截器 =====================
 */
instance.interceptors.request.use(
    (config) => {
        // 全局loading
        handleLoading(config.showLoading);

        // 追加token
        const token = useUserStore.getState().token;
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }

        return config;
    },
    (error: AxiosError) => {
        return Promise.reject(error);
    }
);

/**
 * ============ 3. 响应拦截器 =====================
 */
instance.interceptors.response.use(
    (response: AxiosResponse) => {
        // 2xx 范围内的状态码都会触发该函数
        return response;
    },
    (error: AxiosError) => {
        // 超出 2xx 范围的状态码都会触发该函数。
        return Promise.reject(error);
    }
);

/**
 * ============ 4. 通用请求方法 ===================
 * @param config 请求配置参数
 * @returns 返回Promise对象，解析值为BaseResponse<T>类型
 * @template T 泛型参数，用于指定返回数据的类型
 */
const requestInstance = async <T = any>(extendedConfig: AxiosRequestConfig): Promise<IResponseData<T>> => {
    // 初始化默认扩展配置参数
    const { showMessage = false, showError = true, ...config } = extendedConfig;

    // TODO:重复请求过滤
    try {
        // 二次拦截:处理成功情况
        const response = await instance.request<any, AxiosResponse<IResponseData<T>, any>, any>(config);
        // `data` 由服务器提供的响应
        const res = response.data;

        // 后端code约定，code为000000时表示请求成功处理完成返回数据
        if (res.code !== '000000') {
            // 如果配置了 showError，先行弹窗， handled 标记为 true，避免全局重复提示
            if (showError) {
                // 抛出业务错误，保留 code 与 message，类型为业务异常，ErrorBoundary组件会捕获到这个错误，并且不降级处理
                throw new BizError(res.code, res.msg || '处理异常，请稍后再试', !!showError);
            }
        }

        // 是否全局弹框显示成功消息
        if (showMessage) {
            message.success(res.msg);
        }

        // 返回数据
        return res;
    } catch (error) {
        // 二次拦截:处理失败情况
        handleError(error as AxiosError & Error, showError);

        // 抛出错误，让调用者捕获到这个错误,以便 (try...catch...结构捕获到这个错误，并进行后续的自定义错误处理，例如创建用户失败，显示用户已存在的提示)
        throw error;
    } finally {
        // 二次拦截:总是会执行
        // 隐藏全局loading
        handleLoading(false);
    }
};
/*
 * ============ 5. 工厂函数生成请求方法 ============
 */
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
const createMethod = (method: Method) => {
    return <T = any>(
        url: string,
        dataOrParams?: Record<string, any>,
        // 请求配置，内部类型已经扩展
        config: AxiosRequestConfig = {}
    ): Promise<IResponseData<T>> => {
        const key = ['GET', 'DELETE'].includes(method) ? 'params' : 'data';
        return requestInstance({ url, method, [key]: dataOrParams, ...config });
    };
};
/*
 * ============ 6. 文件下载工具 ==================
 * 后端接口需返回二进制流，设置正确的 Content-Type、Content-Disposition，并可自定义文件名响应头，前端才能用 blob 正常下载文件。
 */
const getFileNameFromDisposition = (disposition?: string): string | undefined => {
    if (!disposition) return undefined;
    const match = disposition.match(/filename="?([^"]+)"?/);
    return match ? decodeURIComponent(match[1]) : undefined;
};

type DownloadFileOptions = {
    data?: Record<string, any>;
    fileName?: string;
    config?: AxiosRequestConfig;
};

const downloadFile = async (url: string, options: DownloadFileOptions = {}): Promise<void> => {
    const { data, fileName = 'filename.xlsx', config = {} } = options;
    const { showMessage = false, showError = true } = config;
    try {
        handleLoading((config as any).showLoading);
        const response: AxiosResponse<Blob> = await instance.request({
            url,
            method: 'POST',
            data,
            ...config,
            responseType: 'blob'
        });
        // 实例化Blob实例
        const blob = new Blob([response.data], { type: response.data.type });
        // 优先取响应头 file-name，其次 Content-Disposition，最后参数
        const resHeaders = response.headers;
        const name =
            (resHeaders['file-name'] as string) ||
            getFileNameFromDisposition(resHeaders['content-disposition']) ||
            fileName;
        // 创建下载链接
        const link = document.createElement('a');
        // 保存文件名称，解构防止乱码
        link.download = decodeURIComponent(name);
        // 构造下载链接(file or blob 对象创建为url 也可用于本地上传图片预览)
        link.href = URL.createObjectURL(blob);
        // 追加到body
        document.body.appendChild(link);
        // 触发下载
        link.click();
        // 移除链接
        document.body.removeChild(link);
        // 释放对象URL,释放内存
        window.URL.revokeObjectURL(link.href);
        // 是否全局弹框显示成功消息
        if (showMessage) {
            message.success('下载成功');
        }
    } catch (error) {
        handleError(error as AxiosError & Error, showError);
        throw error;
    } finally {
        handleLoading(false);
    }
};

/**
 * ============ 7. 导出API对象 ==================
 */
export default {
    get: createMethod('GET'),
    post: createMethod('POST'),
    put: createMethod('PUT'),
    patch: createMethod('PATCH'),
    del: createMethod('DELETE'),
    downloadFile
};
